﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;


using Utils.Cache.Redis;

namespace Utils
{


    public class SeckillHelper<K> where K : IConvertible
    {
        private const string KEY = "SECKILL";

        private readonly ConcurrentDictionary<K, SeckillEntity> seckills = new ConcurrentDictionary<K, SeckillEntity>();

        private readonly RedisCacheStrategy redisCacheStrategy;

        public SeckillHelper(RedisCacheStrategy redisCacheStrategy)
        {
            this.redisCacheStrategy = redisCacheStrategy.Change(5) as RedisCacheStrategy;
        }

        public void Init(K key, Func<K, int> stock, int initialCount = 1, int maxCount = 100)
        {
            lock (this)
            {
                if (!seckills.ContainsKey(key))
                {
                    seckills.TryAdd(key, new SeckillEntity(key, stock, redisCacheStrategy, initialCount, maxCount));
                }
            }
        }

        public Task<TResult> Seckill<TResult>(K key, int qty, int millisecondsTimeout, Func<Task<TResult>> func)
        {
            if (seckills.TryGetValue(key, out var seckill))
            {
                return seckill.LockStock(qty, millisecondsTimeout, func);
            }
            throw Error.DefaultError("活动未开始");
        }

        public void Remove(K key)
        {
            if (redisCacheStrategy != null)
            {
                redisCacheStrategy.DeleteHash(KEY, key.ToString());
            }
            seckills.TryRemove(key, out _);
        }

        public int? GetStock(K key)
        {
            if (seckills.TryGetValue(key, out var value))
            {
                return value.GetStock();
            }
            return null;
        }

        /// <summary>
        /// 增加库存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="qty"></param>
        public void IncrementStock(K key, int qty)
        {
            if (seckills.TryGetValue(key, out var value))
            {
                value.IncrementStock(qty);
            }
        }


        /// <summary>
        /// 减少库存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="qty"></param>
        public void DecrementStock(K key, int qty)
        {
            if (seckills.TryGetValue(key, out var value))
            {
                value.DecrementStock(qty);
            }
        }

        class SeckillEntity
        {

            public SeckillEntity(K key, Func<K, int> getStock, RedisCacheStrategy redisCacheStrategy, int initialCount, int maxCount)
            {
                this._key = key;
                this.key = key.ToString();
                this.getOriginalStock = getStock;
                this.cacheStrategy = redisCacheStrategy;
                this.asyncLock = new AsyncLock(initialCount, maxCount);
                this.GetStock();
            }

            private readonly K _key;

            private readonly string key;

            private readonly Func<K, int> getOriginalStock;

            private readonly RedisCacheStrategy cacheStrategy;

            private readonly AsyncLock asyncLock;

            private const string KEY = "NFT_SECKILL";

            private string LockKey => $"{KEY}_{key}";



            private void RedisLock(Action action, int millisecondsTimeout)
            {
                cacheStrategy.Lock(LockKey, action, millisecondsTimeout);
            }

            private T RedisLock<T>(Func<T> action, int millisecondsTimeout)
            {
                return cacheStrategy.Lock(LockKey, action, millisecondsTimeout);
            }



            public int GetStock()
            {
                if (cacheStrategy.GetDatabase().HExists(KEY, key))
                {
                    return cacheStrategy.GetHash<int>(KEY, key);
                }
                return RedisLock(() =>
                {
                    if (cacheStrategy.GetDatabase().HExists(KEY, key))
                    {
                        return cacheStrategy.GetHash<int>(KEY, key);
                    }
                    var stock = getOriginalStock(_key);
                    cacheStrategy.AddHash(KEY, key, stock);
                    return stock;
                }, 1000);
            }




            public async Task<T> LockStock<T>(int qty, int millisecondsTimeout, Func<Task<T>> func)
            {
                //列队锁
                using (await asyncLock.Lock(millisecondsTimeout))
                {

                    // 事物锁
                    RedisLock(() =>
                    {
                        if (!cacheStrategy.GetDatabase().HExists(KEY, key))
                        {
                            var stock = getOriginalStock(_key);

                            cacheStrategy.AddHash(KEY, key, stock);
                        }
                    }, millisecondsTimeout);

                    DecrementStock(qty);
                    try
                    {
                        T result = await func();
                        return result;
                    }
                    catch (Exception)
                    {
                        IncrementStock(qty);
                        throw;
                    }
                }
            }

            /// <summary>
            /// 增加库存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="qty"></param>
            public void IncrementStock(int qty)
            {
                cacheStrategy.GetDatabase().HIncrBy(KEY, key, qty);
            }

            /// <summary>
            /// 减少库存
            /// </summary>
            /// <param name="key"></param>
            /// <param name="qty"></param>
            public void DecrementStock(int qty)
            {
                var result = cacheStrategy.GetDatabase().HIncrBy(KEY, key, -qty);
                if (result < 0)
                {
                    IncrementStock(qty);
                    throw Error.DefaultError("库存不足");
                }
            }


        }
    }

}
